查看原文
其他

让Python中类的属性具有惰性求值的能力

点击上方“Python编程时光”,选择“加为星标

第一时间关注Python技术干货!


转自知乎:https://zhuanlan.zhihu.com/p/42094516

作者:weapon

1. 背景

我们希望将一个只读的属性定义为 property 属性方法,只有在访问它时才进行计算,但是,又希望把计算出的值缓存起来,不要每次访问它时都重新计算。

2. 解决方案

定义一个惰性属性最有效的方法就是利用描述符类来完成它,示例如下:

class lazyproperty:
    def __init__(self, fun):
        self.fun = fun

    def __get__(self, instance, owner):
        if instance is None:
            return self
        value = self.fun(instance)
        setattr(instance, self.fun.__name__, value)
        return value

要使用这个工具,可以像下面的方式来使用它:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @lazyproperty
    def area(self):
        print('Computing area')
        return 3.1415 * self.radius ** 2

c = Circle(5)
print(c.area)
print(c.area)

可以看出,这里的实例方法 area() 只会被调用一次。

3. 为什么会这样

如果类中定义了 __get__()__set__()__delete__() 中的任何方法,那么这个就被成为描述符(descriptor)。

一般情况下(我是说一般情况下),访问属性的默认行为是从对象的字典中获取,并沿着一个查找链的顺序进行搜索,比如对于 a.x 有一个查找链,从 a.__dict__['x'] 然后是 type(a).__dict__['x'],再继续通过 type(a) 的基类开始。

而如果查找的值是一个描述符对象,则会覆盖这个默认的搜索行为,优先采用描述符的行为,这个行为会因为如果调用而有些不同。这里就只说明例子中的情况。

如果描述符绑定的对象实例,a.x 则转换为调用: type(a).__dict__['x'].__get__(a, type(a))

当一个描述符之定义 __get__() 方法,则它的绑定关系比一般情况下要弱化很多。特别是,只有当被访问的属性不存在对象字典中时,__get__() 才会被调用。

更多描述可见文档:

3. Data model - Python 3.7.0 documentationdocs.python.org

这种惰性求值的方法在很多模块中都会使用,比如django中的 cached_property


使用上与例子一致,如表单中的 changed_data :

4. 继续讨论

在大部分情况下,让属性具有惰性求值能力的全部意义就在于提升程序性能。当不需要这个属性时就能避免进行无意义的计算,同时又能阻止该属性重复进行计算。

本文的技巧中有一个潜在的缺点,就是计算出的值后就变成可变的(mutable)。

>>> c.area
78.53
>>> c.area = 3
>>> c.area
3

如果考虑可变性的问题,可以使用另一种实现方式,但执行效率会稍打折扣:

def lazyproperty(func):
    name = '_lazy_' + func.__name__
    @property
    def lazy(self):
        if hasattr(self, name):
            return getattr(self, name)
        value = func(self)
        setattr(self, name, value)
        return value

    return lazy

如果使用这种方式,就会发现 set 操作是不允许的,所有的 get 操作都必须经由属性的 getter 函数来处理,这比直接在实例字典中查找相应的值要慢一些。

5. 参考阅读

  1. https://docs.python.org/3/reference/datamodel.html?#object.__get__

  2. 《Python Cookbook 第三版》



推荐阅读


【神技巧】巧用 traceback 定位 Python 内存泄漏

太强了!Python中完美的日志解决方案

如何使用 Python 操作 Git 代码?

情人节来了,教你个用 Python 表白的技巧

11 个最佳的 Python 编译器和解释器

用它5分钟以后,我放弃用了四年的 Flask




    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存